#include "drivers.h"
#include "process.h"
#include "vehicle.h"
#include "transmit.h"
#include "eobd.h"
#include "gm.h"
#include "toyota.h"
#include "vag.h"
#include "haval.h"
#include "hyundai.h"
#include "senova.h"

#define VEHICLE_TASK_INTERVAL                  600

Vehicle mVehicle;

OS_TCB VehicleTaskTCB;
CPU_STK VEHICLE_TASK_STK[VEHICLE_TASK_STK_SIZE];

OS_TCB VehicleControlTaskTCB;
CPU_STK VEHICLE_CONTROL_TASK_STK[VEHICLE_CONTROL_TASK_STK_SIZE];
#define CONTROL_QUEUE_SIZE                      10
#define CONTROL_PENDING_TIMEOUT                 5

VinCode mVinCode;
EngineDataMap mEngDataMap;
BcmDataMap mBcmDataMap;
IpcDataMap mIpcDataMap;
uint8_t mEngineLastStatus;

OS_TMR rx_timer;
uint8_t rx_flag;

void vehicle_init(void)
{
    uint8_t i;
    OS_ERR err;

    mVehicle.inited = FALSE;
    mVehicle.engine_on = FALSE;
    OSMutexCreate(
            (OS_MUTEX       *)&mVehicle.lock,
            (CPU_CHAR       *)"VEHICLE MUTEX",
            &err
            );
    //create vehicle task
    OSTaskCreate(
            (OS_TCB     *) &VehicleTaskTCB,
            (CPU_CHAR   *) "Vehicle Task",
            (OS_TASK_PTR ) vehicle_task,
            (void       *) 0,
            (OS_PRIO     ) VEHICLE_TASK_PRIO,
            (CPU_STK    *) &VEHICLE_TASK_STK[0],
            (CPU_STK_SIZE) VEHICLE_TASK_STK_SIZE / 10,
            (CPU_STK_SIZE) VEHICLE_TASK_STK_SIZE,
            (OS_MSG_QTY  ) 0,
            (OS_TICK     ) 0,
            (void       *) 0,
            (OS_OPT      ) OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
            (OS_ERR     *) &err
            );
    OSTaskCreate(
            (OS_TCB     *) &VehicleControlTaskTCB,
            (CPU_CHAR   *) "Vehicle Control Task",
            (OS_TASK_PTR ) vehicle_control_task,
            (void       *) 0,
            (OS_PRIO     ) VEHICLE_CONTROL_TASK_PRIO,
            (CPU_STK    *) &VEHICLE_CONTROL_TASK_STK[0],
            (CPU_STK_SIZE) VEHICLE_CONTROL_TASK_STK_SIZE / 10,
            (CPU_STK_SIZE) VEHICLE_CONTROL_TASK_STK_SIZE,
            (OS_MSG_QTY  ) CONTROL_QUEUE_SIZE,
            (OS_TICK     ) 0,
            (void       *) 0,
            (OS_OPT      ) OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR,
            (OS_ERR     *) &err
            );
    //create timer
    OSTmrCreate((OS_TMR *)&rx_timer,
            (CPU_CHAR   *)"RX TIMER",
            (OS_TICK     )OS_CFG_TMR_TASK_RATE_HZ * 1,
            (OS_TICK     )0,
            (OS_OPT      )OS_OPT_TMR_ONE_SHOT,
            (OS_TMR_CALLBACK_PTR)vehicle_rx_timer_callback,
            (void       *)&rx_flag,
            (OS_ERR     *)&err);
    rx_flag = FALSE;

    mEngDataMap.need_update_size = 0;
    for(i = 0; i < ENG_DATA_SIZE; i++) {
        memset(&mEngDataMap.data[i], 0x00, sizeof(ResultData));
    }
    mBcmDataMap.need_update_size = 0;
    mBcmDataMap.size = BCM_DATA_SIZE;
    for(i = 0; i < BCM_DATA_SIZE; i++) {
        memset(&mBcmDataMap.data[i], 0x00, sizeof(ResultData));
    }

    for(i = 0; i < IPC_DATA_SIZE; i++) {
        memset(&mIpcDataMap.data[i], 0x00, sizeof(ResultData));
    }

    mVinCode.updated = FALSE;
    memset(mVinCode.code, 0x00, 17);
    mEngineLastStatus = FALSE;
}

void vehicle_get_rpm(uint16_t *rpm)
{
//    logi("+++%s+++", __func__);
    if(mVehicle.inited              
//            &&
//            mVehicle.processOps     &&
//            mVehicle.processOps->get_rpm
            ) {
        vehicle_lock();
        //set a fake data to enable gps forever
//        *rpm = 4096;
//        mVehicle.processOps->get_rpm(rpm);
        eobd_get_rpm(rpm);
        vehicle_unlock();
    } else {
        loge("%s: invalid to get rpm", __func__);
        *rpm = 0;
    }
//    logi("---%s---", __func__);
}

void vehicle_task(void *unused)
{
    //uint8_t i;

    while(1) {
        os_delay_min(VEHICLE_TASK_INTERVAL / 60);
        //os_delay(20);
        mVehicle.engine_on = vehicle_check_engine();
        logi("engine %s", mVehicle.engine_on
                ? "ON" : "OFF");
        if(mVehicle.inited              &&
                //                mVehicle.engine_on      &&
                mVehicle.processOps     &&
                mVehicle.processOps->process_eng_data) {
//            transmit_lock();
            mVehicle.processOps->process_eng_data(&mEngDataMap);
//            transmit_unlock();
#if 1
            dump_eng_result(&mEngDataMap);
#endif
        }
        if(mVehicle.inited              &&
                mVehicle.processOps     &&
                mVehicle.processOps->process_vin_code) {
//            transmit_lock();
            mVehicle.processOps->process_vin_code(&mVinCode);
//            transmit_unlock();
        }
        if(mVehicle.inited              &&
                mVehicle.processOps     &&
                mVehicle.processOps->process_bcm_data) {
//            transmit_lock();
            mVehicle.processOps->process_bcm_data(&mBcmDataMap);
//            transmit_unlock();
#if 1
            dump_bcm_result(&mBcmDataMap);
#endif
        }
        if(mVehicle.inited              &&
                mVehicle.processOps     &&
                mVehicle.processOps->process_ipc_data) {
//            transmit_lock();
            mVehicle.processOps->process_ipc_data(&mIpcDataMap);
//            transmit_unlock();
#if 1
            dump_ipc_result(&mIpcDataMap);
#endif
        }
    }
}

uint8_t vehicle_check_engine(void)
{
    uint8_t on = FALSE;

    if(mVehicle.inited              &&
            mVehicle.processOps     &&
            mVehicle.processOps->check_engine_on) {
        vehicle_lock();
        on = mVehicle.processOps->check_engine_on();
        vehicle_unlock();
    } else {
        on = FALSE;
    }

    if(mEngineLastStatus == FALSE && on == TRUE) {
        vehicle_engine_trigger();
    }
    mEngineLastStatus = on;
    return on;
}

void vehicle_engine_trigger(void)
{
    FaultCodeItem items[CODE_SIZE];
    uint8_t i;
    uint8_t j;

    logi("##%s: process codes##", __func__);
    for(i = 0; i < CODE_SIZE; i++) {
        items[i].type = i;
        items[i].count = 0;
        if(mVehicle.inited              &&
                mVehicle.processOps     &&
                mVehicle.processOps->get_fault_code) {
            mVehicle.processOps->get_fault_code(i, &items[i]);
        }
        logi("code %d count = %d", i, items[i].count);
        for(j = 0; j < items[i].count; j++) {
            logi("code[%d] = %04x", j, items[i].codes[j]);
        }
        transmit_codes_data(&items[i]);
    }
}

void vehicle_control_task(void *unused)
{
    OS_ERR err;
    OS_MSG_SIZE size;
    CtrlMsg *ctrlMsg;
    uint8_t id;
    uint8_t val;
    uint32_t cmd_id;

    while(1) {
        ctrlMsg = OSTaskQPend(
                (OS_TICK)0,
                (OS_OPT)OS_OPT_PEND_BLOCKING,
                (OS_MSG_SIZE*)&size,
                (CPU_TS*)0,
                (OS_ERR*)&err
                );
        id = ctrlMsg->id;
        val = ctrlMsg->value;
        cmd_id = ctrlMsg->cmd_id;
        //vehicle_lock();
        logi("######%s: id = %d, cmd_id = %d", __func__, id, cmd_id);
        switch(id) {
            case CONTROL_WINDOW:
                if(mVehicle.inited                                           &&
                        (mVehicle.ctrlOps)                                   &&
                        (mVehicle.ctrlOps->support & SUPPORT_CONTROL_WINDOW) &&
                        (mVehicle.ctrlOps->control_window)) {
                    mVehicle.ctrlOps->control_window(val);
                } else {
                    loge("%s: not support window control", __func__);
                }
                logi("%s: window %s", __func__, (val == 1) ? "ON" : "OFF");
                break;
            case CONTROL_DOOR:
                if(mVehicle.inited                                           &&
                        (mVehicle.ctrlOps)                                   &&
                        (mVehicle.ctrlOps->support & SUPPORT_CONTROL_DOOR)   &&
                        (mVehicle.ctrlOps->control_door)) {
                    mVehicle.ctrlOps->control_door(val);
                } else {
                    loge("%s: not support door control", __func__);
                }
                logi("%s: door %s", __func__, (val == 1) ? "ON" : "OFF");
                break;
            case CONTROL_LIGHT:
                if(mVehicle.inited                                           &&
                        (mVehicle.ctrlOps)                                   &&
                        (mVehicle.ctrlOps->support & SUPPORT_CONTROL_LIGHT)   &&
                        (mVehicle.ctrlOps->control_light)) {
                    mVehicle.ctrlOps->control_light(val);
                } else {
                    loge("%s: not support light control", __func__);
                }
                logi("%s: light %s", __func__, (val == 1) ? "ON" : "OFF");
                break;
            case CONTROL_SUNROOF:
                if(mVehicle.inited                                           &&
                        (mVehicle.ctrlOps)                                   &&
                        (mVehicle.ctrlOps->support & SUPPORT_CONTROL_SUNROOF)   &&
                        (mVehicle.ctrlOps->control_sunroof)) {
                    mVehicle.ctrlOps->control_sunroof(val);
                } else {
                    loge("%s: not support sunroof control", __func__);
                }
                logi("%s: sunroof %s", __func__, (val == 1) ? "ON" : "OFF");
                break;
            case CONTROL_TRUNK:
                if(mVehicle.inited                                           &&
                        (mVehicle.ctrlOps)                                   &&
                        (mVehicle.ctrlOps->support & SUPPORT_CONTROL_TRUNK)   &&
                        (mVehicle.ctrlOps->control_trunk)) {
                    mVehicle.ctrlOps->control_trunk(val);
                } else {
                    loge("%s: not support trunk control", __func__);
                }
                logi("%s: trunk %s", __func__, (val == 1) ? "ON" : "OFF");
                break;
            case CONTROL_FINDCAR:
                if(mVehicle.inited                                           &&
                        (mVehicle.ctrlOps)                                   &&
                        (mVehicle.ctrlOps->support & SUPPORT_CONTROL_FINDCAR)   &&
                        (mVehicle.ctrlOps->control_findcar)) {
                    mVehicle.ctrlOps->control_findcar(val);
                } else {
                    loge("%s: not support findcar control", __func__);
                }
                logi("%s: findcar %s", __func__, (val == 1) ? "ON" : "OFF");
                break;
            case CONTROL_IMMOLOCK:
                logi("####immo = %d", val);
                if(val == 1) {
                    set_lock();
                    bsp_can_enable(FALSE);
                } else {
                    set_unlock();
                    bsp_can_enable(TRUE);
                }
                break;

            default:
                loge("%s: invalid id", __func__);
                break;
        }
        //vehicle_unlock();
        transmit_control_rsp(cmd_id, id);
    }
}

void vehicle_setup(VehicleTypeEnum type)
{
    switch(type) {
        case VEHICLE_TOYOTA:
            bsp_can_init(CAN_500K);
            toyota_init(&mVehicle);
            break;
        case VEHICLE_GM:
            bsp_can_init(CAN_500K);
            gm_init(&mVehicle);
            break;
        case VEHICLE_VAG:
            bsp_can_init(CAN_500K);
            vag_init(&mVehicle);
            break;
        case VEHICLE_LAVIDA:
            bsp_can_init(CAN_500K);
            lavida_init(&mVehicle);
            break;
        case VEHICLE_HAVAL:
            bsp_can_init(CAN_500K);
            haval_init(&mVehicle);
            break;
        case VEHICLE_HYUNDAI:
            bsp_can_init(CAN_500K);
            hyundai_init(&mVehicle);
            break;
        case VEHICLE_SENOVA:
            bsp_can_init(CAN_125K);
            senova_init(&mVehicle);
			break;
        case VEHICLE_EOBD:
            bsp_can_init(CAN_500K);
            eobd_init(&mVehicle);
            break;
        default:
            break;
    }
    mVehicle.inited = TRUE;
    mVehicle.vehicle_type = type;
}

void vehicle_lock(void)
{
    OS_ERR err;

    //    logi("++%s++", __func__);
    OSMutexPend(&mVehicle.lock, 0, OS_OPT_PEND_BLOCKING, 0, &err);
}

void vehicle_unlock(void)
{
    OS_ERR err;

    //    logi("--%s--", __func__);
    OSMutexPost(&mVehicle.lock, OS_OPT_POST_NONE, &err);
}

static void vehicle_rx_timer_callback(void *p_arg)
{
    //loge("%s: callback timeout", __func__);
    rx_flag = TRUE;
}

void vehicle_rx_start_timer(uint16_t timeout)
{
    OS_ERR err;

    OSTmrSet((OS_TMR *)&rx_timer,
            (OS_TICK     )OS_CFG_TMR_TASK_RATE_HZ * timeout,
            (OS_TICK     )0,
            (OS_TMR_CALLBACK_PTR)vehicle_rx_timer_callback,
            (void       *)0,
            (OS_ERR     *)&err);
    OSTmrStart((OS_TMR *)&rx_timer, &err);
    if(err != OS_ERR_NONE) {
        loge("start timer failed, err = %d", err);
    }
}

void vehicle_rx_stop_timer(void)
{
    OS_ERR err;

    OSTmrStop((OS_TMR *)&rx_timer, OS_OPT_TMR_NONE, (void *)0, &err);
    if(err != OS_ERR_NONE) {
        loge("stop timer failed, err = %d", err);
    }
}

uint8_t vehicle_can_session(CanTxMsg *txMsg, CanRxMsg *rxMsg)
{
    uint8_t ret = FALSE;

    rx_flag = FALSE;
    bsp_can_clear();
    if(txMsg != NULL) {
        bsp_can_send(txMsg);
    }
    vehicle_rx_start_timer(1);
    while(1) {
        os_delay_ms(2);
        if(rx_flag) {
            //loge("%s: timeout", __func__);
            ret  = FALSE;
            break;
        }
        if(bsp_can_recv(rxMsg)) {
            //            logi("%s: recv can package", __func__);
            ret = TRUE;
            vehicle_rx_stop_timer();
            break;
        }
    }

    return ret;
}

uint8_t vehicle_can_recv(CanRxMsg *rxMsg)
{
    uint8_t ret = FALSE;

    rx_flag = FALSE;
    vehicle_rx_start_timer(1);
    while(1) {
        os_delay_ms(1);
        if(rx_flag) {
            //loge("%s: timeout", __func__);
            ret  = FALSE;
            break;
        }
        if(bsp_can_recv(rxMsg)) {
            //            logi("%s: recv can package", __func__);
            ret = TRUE;
            vehicle_rx_stop_timer();
            break;
        }
    }

    return ret;
}

uint8_t vehicle_get_type(void)
{
    return mVehicle.vehicle_type;
}

void vehicle_set_engine_on(uint8_t on)
{
    mVehicle.engine_on = on;
}

void dump_eng_result(EngineDataMap *dataMap)
{
    uint8_t i, j;
    uint8_t update;

    logi("------------------------------");
    for(i = 0; i < ENG_DATA_SIZE; i++) {
        update = dataMap->data[i].updated;
        if(update) {
            printf("\t--> %16s  %d | ",
                    transmit_get_key(i), dataMap->data[i].len);
            for(j = 0; j < dataMap->data[i].len; j++) {
                printf("%02x ", dataMap->data[i].data[j]);
            }
            printf("\r\n");
        }
    }
    logi("------------------------------");
}

void dump_bcm_result(BcmDataMap *dataMap)
{
    uint8_t i, j;
    uint8_t update;

    logi("------------------------------");
    for(i = 0; i < BCM_DATA_SIZE; i++) {
        update = dataMap->data[i].updated;
        if(update) {
            printf("\t--> %16s  %d | ",
                    transmit_get_bcm_key(i), dataMap->data[i].len);
            for(j = 0; j < dataMap->data[i].len; j++) {
                printf("%02x ", dataMap->data[i].data[j]);
            }
            printf("\r\n");
        }
    }
    logi("------------------------------");
}

void dump_ipc_result(IpcDataMap *dataMap)
{
    uint8_t i, j;
    uint8_t update;

    logi("------------------------------");
    for(i = 0; i < IPC_DATA_SIZE; i++) {
        update = dataMap->data[i].updated;
        if(update) {
            printf("\t--> %16s  %d | ",
                    transmit_get_ipc_key(i), dataMap->data[i].len);
            for(j = 0; j < dataMap->data[i].len; j++) {
                printf("%02x ", dataMap->data[i].data[j]);
            }
            printf("\r\n");
        }
    }
    logi("------------------------------");
}

int16_t index_of_buf(uint8_t *buf, uint8_t size, uint8_t value)
{
    uint16_t i;
    int16_t index = -1;

    for(i = 0; i < size; i++) {
        if(buf[i] == value) {
            index = i;
            break;
        }
    }

    return index;
}

